#include <string>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <memory>
#include "Pocelain.h"
#include "../../controls/OrbitControl.hpp"

using namespace std;
POCELAIN_NAMESPACE_USING
    string shader_vert = R"EOF(
#version 330
layout(location=0) in vec3 position;
layout(location=1) in vec3 normal;
layout(location=2) in vec2 uv;
uniform mat4x4 modelViewMatrix;
uniform mat4x4 projectionMatrix;
uniform mat3x3 normalMatrix;
out vec3 vNormal;
out vec2 vUv;
out vec3 vWorldPosition;
void main()
{
    vUv = uv;
    vNormal = normalize(normalMatrix * normal);
    vWorldPosition = (modelViewMatrix * vec4(position,1.0)).xyz;
    gl_Position = projectionMatrix*vec4 (vWorldPosition,1.0);

})EOF";

string shader_frag = R"EOF(
#version 330
out vec4 color;
in vec3 vNormal;
in vec2 vUv;
in vec3 vWorldPosition;

uniform sampler2D myTextureSampler;
uniform vec3 viewPosition;

const vec3 lightDirection = vec3(0.0,-1.0,1.0);
const vec3 ambient = vec3(0.3,0.3,0.3);
const vec3 lightColor = vec3(0.0,0.8,0.0);

vec3 packNormalToRGB( const in vec3 normal ) {
    return normalize( normal ) * 0.5 + 0.5;
}

void main()
{
    vec3 viewDir = normalize(viewPosition - vWorldPosition);
    float diffuse = max(0.0,dot(vNormal,lightDirection));
    float specular = max(0.0,dot(vNormal,viewDir));
    vec3 reflectDir = normalize(reflect(-lightDirection,vNormal));

    if(diffuse ==0.0)
    {
        specular = 0.0;
    }
    else
    {
        specular = pow(specular , 100);
    }

    // 法线贴图
    color = vec4(packNormalToRGB(vNormal),1.0);
    //color = vec4(ambient + specular + lightColor * diffuse,1.0);
})EOF";

template<class T>
void printMatrix(T mat)
{
    for(size_t r = 0 ; r < mat.numOfRows(); ++r){
        cout<<'|';
        for(size_t c = 0 ; c < mat.numOfCols(); ++c){
            cout << mat[r * mat.numOfCols() + c];
            if(c!=mat.numOfCols()-1){
                cout<<",\t";
            }
        }
        cout<<'|'<<endl;
    }
    cout<<endl;
}
void printMatrix4(const Matrix4 &mat)
{
    printMatrix(mat);
}

class Application: public ApplicationCoreProfile
{
private:
    OpenGLShaderProgram *program= nullptr;
    Texture texture;
    PerspectiveCamera *camera = nullptr;
    Scene *scene = nullptr;
    OpenGLRenderer *renderer = nullptr;
    Uniform<Vector3> *uniformViewPosition = nullptr;
    Uniform<Matrix4> *uniformModelViewMatrix = nullptr;
    Uniform<Matrix4> *uniformProjectionMatrix = nullptr;
    //    Uniform<Matrix4> *uniformMVP = nullptr;
    Uniform<Matrix3> *uniformNormalMatrix = nullptr;
    OrbitControl *control;
public:

    Application(int argc , char **argv)
        :ApplicationCoreProfile(argc,argv)
    {
    }
    ~Application()
    {
        camera->release();
        if(program!=nullptr)
        {
            program->release();
        }
        if(scene!=nullptr)
        {
            scene->release();
        }
        if(renderer!=nullptr)
        {
            renderer->release();
        }
    }

    void init()override
    {
        AutoReleasePool();

        ApplicationCoreProfile::init();

        camera = CreateRef<PerspectiveCamera>(25,mWindowSize.x/mWindowSize.y );

        camera->setPosition(Vector3(10,0,0));

        camera->lookAt(Vector3(0,0,0));

        scene = CreateRef<Scene>();

        auto geo = CreateRef<CircleGeometry>();

        auto material = new RawShaderMaterial();

        auto mesh = CreateRef<Mesh>(geo,material);

        auto dirLight = CreateRef<DirectionalLight>();

        dirLight->setPosition(Vector3(- 1, 0, 1 ).normalize());

        scene->add( dirLight );

        control = new OrbitControl(mInput,camera);

        scene->add(mesh);

        scene->add(control);

        renderer = new OpenGLRenderer(mWindow,mInput);

        material->shaderSource(EShaderType::E_VERTEX,shader_vert);

        material->shaderSource(EShaderType::E_FRAGMENT,shader_frag);

        material->setDepthTest(true);

        material->setDepthWrite(true);

        //        uniformMVP = new Uniform<Matrix4>();
        uniformModelViewMatrix = new Uniform<Matrix4>();
        uniformProjectionMatrix = new Uniform<Matrix4>();
        uniformNormalMatrix = new Uniform<Matrix3>();
        uniformViewPosition = new Uniform<Vector3>();

        //        material->setUniformByName("mvp",uniformMVP);
        material->setUniformByName("normalMatrix",uniformNormalMatrix);
        material->setUniformByName("modelViewMatrix",uniformModelViewMatrix);
        material->setUniformByName("projectionMatrix",uniformProjectionMatrix);
        material->setUniformByName("viewPosition",uniformViewPosition);

    }
    virtual void onResize(int width , int height)override
    {
        ApplicationBase::onResize(width,height);
        camera->setAspect(Real(width)/Real(height));
        camera->updateProjectionMatrix();
    }
    void render(double elapse) override
    {

        control->update(elapse);

        if(mInput->getKey(EKeyCode::Tab))
        {
            auto currState = mInput->getCursorState();
            mInput->setCursorState(
                currState==IInput::ECursorState::Normal?
                    IInput::ECursorState::Disable:
                    IInput::ECursorState::Normal
                );
        }

        Matrix4 modelWorldMatrix;
        Matrix4 projectionMatrix = camera->getProjectionMatrix();
        Matrix4 viewMatrix = camera->getMatrixWorldInverse();

        Matrix4 modelViewMatrix = viewMatrix * modelWorldMatrix;
        Matrix3 normalMatrix;
        normalMatrix.getNormalMatrix(modelViewMatrix);

        //        cout<<"modelMatrix:"<<endl;
        //        printMatrix(matModel);
        //        cout<<"projectionMatrix:"<<endl;
        //        printMatrix(projectionMatrix);
        //        cout<<"viewMatrix:"<<endl;
        //        printMatrix(viewMatrix);
        //        Matrix4 mvp = projectionMatrix * modelViewMatrix;
        //        cout<<"mvp:"<<endl;
        //        printMatrix(mvp);

        //        uniformMVP->setValue(mvp);
        uniformModelViewMatrix->setValue(modelViewMatrix);
        uniformProjectionMatrix->setValue(projectionMatrix);
        uniformNormalMatrix->setValue(normalMatrix);
        uniformViewPosition->setValue(camera->getPosition());

        renderer->render(scene,camera);
    }
};

int main(int argc,char **argv)
{
    Application app(argc,argv);
    return app.run();
}
